package com.apobates.forum.orize.core;

import com.apobates.forum.orize.OrizeExecutor;
import com.apobates.forum.orize.OrizeExpression;
import com.apobates.forum.orize.OrizeMatchResult;
import com.apobates.forum.orize.OrizeResource;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import java.net.URL;
import java.util.*;
import java.util.function.BiPredicate;

/**
 * 抽取公共的方法
 * @author xiaofanku@live.cn
 * @since 20210822
 */
public abstract class OrizeAuthCommonHelper {
    private final static Logger logger = LoggerFactory.getLogger(OrizeAuthCommonHelper.class);
    private final static String NEWLINE = "\r\n";
    public final static String RES_TYPE_XML="xml";
    public final static String RES_TYPE_JSON="json";
    public final static String RES_TYPE_ANNO="anno";

    /**
     * 执行验证
     * @param executor 验证执行器
     * @param memberRolePredicate 用户角色验证谓词表达式
     * @param httpRequest Http请求
     * @param ignoreConfig 配置不需要验证的请求
     * @return
     */
    protected ImmutablePair<Boolean,Map> authentication(OrizeExecutor executor, OrizeMemberRolePredicate memberRolePredicate, HttpServletRequest httpRequest, OrizeAuthIgnoreConfig ignoreConfig){
        //有需要验证的资源吗?
        try {
            URL reqUrl = new URL(httpRequest.getRequestURL().toString());
            if(ignoreConfig.isIgnoreRequest(reqUrl)){
                return ImmutablePair.of(true, Collections.emptyMap());
            }
        }catch (Exception e){}
        return authentication(executor, memberRolePredicate, httpRequest);
    }

    /**
     * 执行验证
     * @param executor 验证执行器
     * @param memberRolePredicate 用户角色验证谓词表达式
     * @param httpRequest Http请求
     * @return left:验证结果(true验证通过,false失败), right:验证日志(message,reqAjax,reqPath,reqMethod)
     */
    protected ImmutablePair<Boolean,Map> authentication(OrizeExecutor executor, OrizeMemberRolePredicate memberRolePredicate, HttpServletRequest httpRequest){
        final boolean isAjax = isAjaxRequest(httpRequest);
        final String reqURI = httpRequest.getRequestURI(), reqQueryString = httpRequest.getQueryString(), reqMethod = httpRequest.getMethod();

        //获取OrizeExpression
        OrizeExpression exp = new OrizeExpression(){
            @Override
            public String method() {
                return reqMethod;
            }
            @Override
            public String path() {
                String _reqFullPath = reqURI;
                if(null!=reqQueryString && !reqQueryString.isEmpty()){
                    _reqFullPath += "?"+reqQueryString;
                }
                return _reqFullPath;
            }
            @Override
            public boolean ajax() {
                return isAjax;
            }
        };
        //执行验证结果
        Optional<OrizeMatchResult> matchRs = Optional.empty();
        try{
            matchRs = checkRole(executor.match(exp), httpRequest, memberRolePredicate.getPredicate());
            printOrizeMatchResult(buildResultLog(matchRs));
        }catch (Exception e){
            e.printStackTrace();
        }
        //下一步呢?
        if(matchRs.isPresent() && !matchRs.get().isCheckSuccess()){ //有结果并且验证失败
            Map<String,String> omrLog = buildResultLog(matchRs);
            if(omrLog.isEmpty()){
                omrLog.put("reqMethod", reqMethod);
                omrLog.put("reqPath", reqURI);
                omrLog.put("reqQuery", reqQueryString);
                //AJAX在响应结果时需要的
                omrLog.put("reqAjax", isAjax?"true":"false");
                omrLog.put("message", "过滤器执行失败");
            }
            //
            return ImmutablePair.of(false, omrLog);
        }
        return ImmutablePair.of(true, Collections.emptyMap());
    }


    /**
     * 是否是ajax请求
     * @param httpRequest Http请求
     * @return true是/false不是
     */
    public static boolean isAjaxRequest(HttpServletRequest httpRequest){
        try {
            return ((httpRequest.getHeader("accept").contains("application/json") || (httpRequest.getHeader("X-Requested-With") != null
                    &&
                    httpRequest.getHeader("X-Requested-With").contains("XMLHttpRequest"))));
        }catch (NullPointerException e){ //SpringBoot时会出现?
            if(logger.isDebugEnabled()){
                logger.debug(e.getMessage(), e);
            }
        }
        return false;
    }

    /**
     * 根据验证结果生成一个日志Map
     * @param result 验证结果
     * @return
     */
    protected Map<String,String> buildResultLog(Optional<OrizeMatchResult> result){
        if(!result.isPresent()){
            return new HashMap<>();
        }
        OrizeMatchResult r = result.get();
        Map<String,String> log = new HashMap<>();
        log.put("reqMethod", r.getExpression().method());
        log.put("reqPath", r.getExpression().path());
        //AJAX在响应结果时需要的
        log.put("reqAjax", r.getExpression().ajax()?"true":"false");
        //显示资源定义
        OrizeResource ors = Optional.ofNullable(r.getResource()).orElse(OrizeResource.notFind());
        log.put("resPath", ors.getPath());
        log.put("resMethod", ors.getMethod());
        log.put("resAction", ors.getAction());
        log.put("resSpot", ors.getSpot());
        log.put("resFind", ors.isNotFind()?"NO":"YES");
        //
        log.put("message", r.getMessage());
        return log;
    }

    /**
     * 不存在资源定义时的默认值
     * @return
     *
    public static OrizeResource notFindResource(){
        return new OrizeResource("-", "get", "*", "do", "view");
    }*/

    /**
     * 根据类型返回资源加载器
     * @param type
     * @param resourcePath 资源所在的位置,需要放在WEB-INF目录下
     * @param webInfPath WEB-INF的位置
     * @return
     */
    protected OrizeResourceLoader getLoader(String type, String resourcePath, String webInfPath){
        Objects.requireNonNull(webInfPath);
        OrizeResourceLoader loader = null;
        switch (type){
            case RES_TYPE_XML:
                loader = (null==resourcePath)?null:OrizeClassicalExecutor.xml(webInfPath + resourcePath);
                break;
            case RES_TYPE_JSON:
                loader = (null==resourcePath)?null:OrizeClassicalExecutor.json(webInfPath + resourcePath);
                break;
            case RES_TYPE_ANNO:
                loader = OrizeClassicalExecutor.annotation(webInfPath);
        }
        return loader;
    }

    /**
     * 返回Filter实现的类全名
     * @return
     */
    protected abstract String getFilterClassName();

    /**
     * 返回一个OrizeMemberQuery实现
     * @return
     * @exception Exception 获取过程中产生的异常
     */
    protected abstract OrizeMemberQuery getMemberQuery() throws Exception;

    protected void printMemberInfo(OrizeMember om){
        String descrip = "[OrizeAuth][SMRL]" + NEWLINE;
        descrip += "/*----------------------------------------------*/" + NEWLINE;
        descrip += " uid: " + om.uid() + NEWLINE;
        descrip += " online: " + (om.onlined()?"on":"off") + NEWLINE;
        descrip += " roles: " + String.join(",", om.roles()) + NEWLINE;
        descrip += "/*----------------------------------------------*/" + NEWLINE;
        if(logger.isDebugEnabled()){
            logger.debug(descrip);
        }
    }

    protected void printOrizeMatchResult(Map matchResultMap){
        String descrip = "[OrizeAuth][SRL]" + NEWLINE;
        descrip += "/*==============================================*/" + NEWLINE;
        descrip += " ReqMethod: " + matchResultMap.get("reqPath") + NEWLINE;
        descrip += " ReqPath: " + matchResultMap.get("reqMethod") + NEWLINE;
        descrip += " Msg: " + matchResultMap.get("message") + NEWLINE;
        descrip += " ResMethod: " + matchResultMap.get("resMethod") + NEWLINE;
        descrip += " ResPath: " + matchResultMap.get("resPath") + NEWLINE;
        descrip += " ResSpot: " + matchResultMap.get("resSpot") + NEWLINE;
        descrip += " ResAction: " + matchResultMap.get("resAction") + NEWLINE;
        descrip += "/*==============================================*/" + NEWLINE;
        if(logger.isDebugEnabled()){
            logger.debug(descrip);
        }
    }

    /**
     * 执行权限验证
     * @param oneStepResult 资源验证结果
     * @param httpRequest Http请求
     * @param memberRolePredicate OrizeMember的谓词表达式
     * @return
     * @throws Exception
     */
    private Optional<OrizeMatchResult> checkRole(OrizeMatchResult oneStepResult, HttpServletRequest httpRequest, BiPredicate<OrizeMember,String[]> memberRolePredicate) throws Exception{
        OrizeMemberQuery omq = getMemberQuery();
        if(null != omq){
            OrizeMember om = omq.query(httpRequest);
            printMemberInfo(om);
            return oneStepResult.check(om, memberRolePredicate);
        }
        return Optional.ofNullable(oneStepResult);
    }

}
